// DirectDraw blitter, modified by regret

//#include "burner.h"

// enum drivers
static GUID myGuid;
static int currentDriver;

static BOOL WINAPI enumDisplayDrivers(GUID FAR* pGuid, LPSTR pszDesc, LPSTR pszName, LPVOID pContext, HMONITOR hMonitor)
{
	if (currentDriver == vidAdapter + 1 && pGuid) {
		memcpy(&myGuid, pGuid, sizeof(GUID));
	}

	if (currentDriver == 0) {
		dprintf(("    %s\n"), pszDesc);
	} else {
		MONITORINFOEX mi;
		mi.cbSize = sizeof(mi);

		GetMonitorInfo(hMonitor, (MONITORINFO*)&mi);

		dprintf(("    Display %d (%s on %s"), currentDriver, mi.szDevice, pszDesc);
		if (mi.dwFlags & MONITORINFOF_PRIMARY) {
			dprintf((", primary"));
		}
		dprintf((")\n"));
	}

	currentDriver++;

	return DDENUMRET_OK;
}

// ddraw
class VideoDDraw : public Video {
public:
	IDirectDraw7* lpDD;					// DirectDraw interface
	IDirectDrawSurface7* lpDDPrim;		// Primary surface
	IDirectDrawSurface7* lpDDBack;		// Back buffer surface
	IDirectDrawSurface7* lpDDScreen;	// The screen surface
	IDirectDrawClipper* clipper;

	RECT rs;
	RECT rd;

	HMODULE ddrawHandle;

	int clearSurface()
	{
		if (!lpDDPrim) {
			return 1;
		}

		vidSClearSurface(lpDDPrim, 0, NULL);		// Clear 1st page

		if (lpDDBack) {								// We're using a triple buffer
			vidSClearSurface(lpDDBack, 0, NULL);	// Clear 2nd page
			lpDDPrim->Flip(NULL, DDFLIP_WAIT);
			vidSClearSurface(lpDDBack, 0, NULL);	// Clear 3rd page
		}

		return 0;
	}

	int primSurfaceInit(int triple)
	{
		DDSURFACEDESC2 ddsd;

		// Create the primary surface
		memset(&ddsd, 0, sizeof(ddsd));
		ddsd.dwSize = sizeof(ddsd);
		if (triple) {								// Make a primary surface capable of triple bufferring
			ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
			ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX | DDSCAPS_VIDEOMEMORY;
			ddsd.dwBackBufferCount = 2;
		} else {
			ddsd.dwFlags = DDSD_CAPS;
			ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
		}

		if (FAILED(lpDD->CreateSurface(&ddsd, &lpDDPrim, NULL))) {
			return 1;
		}

		vidScrnDepth = vidSGetSurfaceDepth(lpDDPrim);

		if (triple) {
			// Get the back buffer
			memset(&ddsd.ddsCaps, 0, sizeof(ddsd.ddsCaps));
			ddsd.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER;

			if (FAILED(lpDDPrim->GetAttachedSurface(&ddsd.ddsCaps, &lpDDBack))) {
				// Failed to make triple buffer
				RELEASE(lpDDPrim)
				return 1;
			}

			clearSurface();							// Clear surfaces
		}

		return 0;
	}

	// Create a secondary DirectDraw surface for the game image
	int secondSurfaceInit()
	{
		if (!lpDD) {
			return 1;
		}

		// allocate buffer in Video memory
		DDSURFACEDESC2 ddsd;
		memset(&ddsd, 0, sizeof(ddsd));
		ddsd.dwSize = sizeof(ddsd);

		ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT;
		ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY;//DDSCAPS_SYSTEMMEMORY;
		ddsd.dwWidth = vidSupport.rounded_power_of_two(vidSupport.getWidth() * filter.zoom(filter.filterId));
		ddsd.dwHeight = vidSupport.rounded_power_of_two(vidSupport.getHeight() * filter.zoom(filter.filterId));

		memset(&ddsd.ddpfPixelFormat, 0, sizeof(DDPIXELFORMAT));
		ddsd.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT);
		ddsd.ddpfPixelFormat.dwFlags = DDPF_RGB;
		ddsd.ddpfPixelFormat.dwRGBBitCount = 32;
		ddsd.ddpfPixelFormat.dwRBitMask = 0xff0000;
		ddsd.ddpfPixelFormat.dwGBitMask = 0x00ff00;
		ddsd.ddpfPixelFormat.dwBBitMask = 0x0000ff;

		if (FAILED(lpDD->CreateSurface(&ddsd, &lpDDScreen, NULL))) {
			return 1;
		}

		vidSClearSurface(lpDDScreen, 0, NULL);

		return 0;
	}

	int scale(void* rect, unsigned width, unsigned height)
	{
		return vidSupport.scaleImage(rect, width, height);
	}

	int release()
	{
		RELEASE(lpDDScreen)
		vidSExitOSD();
		return 0;
	}

	int surfaceInit()
	{
		if (!lpDD) {
			return 1;
		}

		// Make the DirectDraw secondary surface
		if (secondSurfaceInit()) {
			return 1;
		}

		rd.left = 0; rd.right = -1;

		RECT rect = { 0, 0, 0, 0 };
		getClientScreenRect(hVidWnd, &rect);

		scale(&rect, vidSupport.getWidth(), vidSupport.getHeight());

		int nFlags = 2;
		if (vidScrnWidth < 1280) {
			nFlags++;
			if (vidScrnWidth < 1024) {
				nFlags++;
				if (vidScrnWidth < 640) {
					nFlags++;
				}
			}
		}
		vidSInitOSD(nFlags);

		return 0;
	}

	void clear()
	{
		DDBLTFX fx;
		fx.dwSize = sizeof(DDBLTFX);
		fx.dwFillColor = 0x00000000;

		if (lpDDBack) {		// Triple bufferring
			lpDDBack->Blt(0, 0, 0, DDBLT_WAIT | DDBLT_COLORFILL, &fx);
		} else {			// Normal
			if (lpDDBack) {
				lpDDPrim->Blt(0, 0, 0, DDBLT_WAIT | DDBLT_COLORFILL, &fx);
			}
		}
	}

	int exit()
	{
		vidSRestoreScreenMode();

		release();

		RELEASE(clipper)
		RELEASE(lpDDPrim)				// a single call releases all surfaces
		lpDDBack = NULL;

		vidSExit();

		RELEASE(lpDD)

		FreeLibrary(ddrawHandle);
		ddrawHandle = 0;

		return 0;
	}

	int init()
	{
		exit();

		hVidWnd = hScrnWnd;

		ddrawHandle = LoadLibrary(L"ddraw.dll");
		if (!ddrawHandle)
			return 1;

		memset(&myGuid, 0, sizeof(GUID));

		if (vidFullscreen) {
			currentDriver = 0;
			dprintf((" ** Enumerating available DirectDraw drivers:\n"));
			HRESULT (WINAPI* _DirectDrawEnumerateEx)(LPDDENUMCALLBACKEXA, LPVOID, DWORD) =
				(HRESULT (WINAPI*)(LPDDENUMCALLBACKEXA, LPVOID, DWORD))GetProcAddress(ddrawHandle, "DirectDrawEnumerateExA");
			if (_DirectDrawEnumerateEx) {
				_DirectDrawEnumerateEx(enumDisplayDrivers, (void*)this, DDENUM_ATTACHEDSECONDARYDEVICES | DDENUM_DETACHEDSECONDARYDEVICES | DDENUM_NONDISPLAYDEVICES);
			}
		}

		// Get pointer to DirectDraw device
		HRESULT (WINAPI* _DirectDrawCreateEx)(GUID* lpGUID, void** lplpDD, REFIID iid, IUnknown* pUnkOuter) =
			(HRESULT (WINAPI*)(GUID* lpGUID, void** lplpDD, REFIID iid, IUnknown* pUnkOuter ))GetProcAddress(ddrawHandle, "DirectDrawCreateEx");
		if (!_DirectDrawCreateEx) {
			return 1;
		}
		_DirectDrawCreateEx(vidAdapter ? &myGuid : NULL, (void**)&lpDD, IID_IDirectDraw7, NULL);

		vidSInit(lpDD);

		lpDDPrim = NULL;					// No primary surface yet
		lpDDBack = NULL;

		// Remember the changes to the display
		if (vidFullscreen) {
			if (vidSEnterFullscreenMode(0, 0)) {
				return 1;
			}
		} else {
			lpDD->SetCooperativeLevel(hVidWnd, DDSCL_NORMAL);
		}

		{
			// Display amount of free video memory
			DDSCAPS2 ddsCaps2;
			DWORD dwTotal;
			DWORD dwFree;

			memset(&ddsCaps2, 0, sizeof(ddsCaps2));
			ddsCaps2.dwCaps = DDSCAPS_PRIMARYSURFACE;

			dprintf((" ** Starting DirectDraw7 blitter.\n"));

			if (SUCCEEDED(lpDD->GetAvailableVidMem(&ddsCaps2, &dwTotal, &dwFree))) {
				dprintf(("  * Initialising video: Total video memory minus display surface: %.2fMB.\n"), (double)dwTotal / (1024 * 1024));
			}
		}

		if (vidTripleBuffer && vidFullscreen) {
			if (primSurfaceInit(1)) {			// Try to make triple buffer
				dprintf(("  * Warning: Couldn't allocate a triple-buffering surface.\n"));
				// If we fail, fail entirely and make a normal buffer
				RELEASE(lpDDPrim)
			}
		}

		if (!lpDDPrim) {
			// No primary surface yet, so try normal
			if (primSurfaceInit(0)) {
		   		dprintf(("  * Error: Couldn't create primary surface.\n"));
				return 1;
			}
		}

		if (vidFullscreen) {
			lpDD->Compact();
		}

		// init clipper
		lpDD->CreateClipper(0, &clipper, 0);
		clipper->SetHWnd(0, hVidWnd);
		lpDDPrim->SetClipper(clipper);

		if (filter.enable) {
			if (filter.init(filter.filterId, vidSupport.getRotate())) {
				dprintf(("  * Error: Couldn't initialise pixel filter.\n"));
			}
		}

		// Init the buffer surfaces
		if (surfaceInit()) {
			return 1;
		}

		{
			DDSCAPS2 ddsCaps2;
			DWORD dwTotal;
			DWORD dwFree;

			memset(&ddsCaps2, 0, sizeof(ddsCaps2));
			ddsCaps2.dwCaps = DDSCAPS_PRIMARYSURFACE;

			if (SUCCEEDED(lpDD->GetAvailableVidMem(&ddsCaps2, &dwTotal, &dwFree))) {
				dprintf(("  * Initialisation complete: %.2fMB video memory free.\n"), (double)dwFree / (1024 * 1024));
				dprintf(("    Displaying and rendering in %i-bit mode, emulation running in %i-bit mode.\n"), vidScrnDepth, 32);
				dprintf(("    Transferring the image to video memory before blitting.\n"));
				if (vidFullscreen) {
					dprintf(("    Running in fullscreen mode (%i x %i)"), vidScrnWidth, vidScrnHeight);
					if (lpDDBack) {
						dprintf((", using a triple buffer"));
					}
					dprintf((".\n"));
				} else {
					dprintf(("    Running in windowed mode.\n"));
				}
			}
		}

		return 0;
	}

	int render()
	{
		if (!lpDDScreen) {
			return 1;
		}

		if (lpDDPrim->IsLost()) {		// We've lost control of the screen
			if (vidSRestoreOSD()) {
				return 1;
			}

			if (FAILED(lpDD->RestoreAllSurfaces())) {
				return 1;
			}

			clearSurface();
		}

		// Lock the surface so we can write to it
		DDSURFACEDESC2 ddsd;
		memset(&ddsd, 0, sizeof(ddsd));
		ddsd.dwSize = sizeof(ddsd);

		if (FAILED(lpDDScreen->Lock(NULL, &ddsd, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT, NULL))) {
			return 1;
		}

		unsigned char* pd = (unsigned char*)ddsd.lpSurface;
		unsigned pitch = ddsd.lPitch;

		filter.apply(pd, pitch);

		lpDDScreen->Unlock(NULL);

		return 0;
	}

	int frame(bool redraw)
	{
		render();
		return 0;
	}

	int paint(int validate)
	{
		if (!lpDDPrim || !lpDDScreen) {
			return 1;
		}

		if (lpDDPrim->IsLost()) {		// We've lost control of the screen
			return 1;
		}

		if (!vidFullscreen) {			// Check if the window has changed since we prepared the image
			RECT rect = { 0, 0, 0, 0 };
			getClientScreenRect(hVidWnd, &rect);

			scale(&rect, vidSupport.getWidth(), vidSupport.getHeight());

			if (rd.left != rect.left || rd.right != rect.right
				|| rd.top != rect.top || rd.bottom != rect.bottom) {
				validate |= 2;
			}
		}

		// calc
		getClientScreenRect(hVidWnd, &rd);

		scale(&rd, vidSupport.getWidth(), vidSupport.getHeight());

		rs.right = vidSupport.getWidth();
		rs.bottom = vidSupport.getHeight();

		rs.right *= filter.zoom(filter.filterId);
		rs.bottom *= filter.zoom(filter.filterId);

		if (validate & 2) {
			render();
		}

		// vsync, added by regret
		if (vidVSync) {
			RECT window;
			GetWindowRect(hVidWnd, &window);

			BOOL in_vblank = FALSE;
			DWORD line;

			while (true) {
				lpDD->GetScanLine(&line);
				lpDD->GetVerticalBlankStatus(&in_vblank);
				if (in_vblank || line >= window.bottom) {
					break;
				}
			}
		}

		if (lpDDBack) {		// Triple bufferring
			if (FAILED(lpDDBack->Blt(&rd, lpDDScreen, &rs, DDBLT_ASYNC, 0))) {
				if (FAILED(lpDDBack->Blt(&rd, lpDDScreen, &rs, DDBLT_WAIT, 0))) {
					return 1;
				}
			}

			vidSDisplayOSD(lpDDBack, &rd, 0);

			lpDDPrim->Flip(NULL, DDFLIP_WAIT);
		} else {			// Normal
			RECT rect = { 0, 0, vidSupport.getWidth(), vidSupport.getHeight() };

			// Display OSD text message
			rect.right *= filter.zoom(filter.filterId);
			rect.bottom *= filter.zoom(filter.filterId);

			vidSDisplayOSD(lpDDScreen, &rect, 0);

			if (FAILED(lpDDPrim->Blt(&rd, lpDDScreen, &rs, DDBLT_ASYNC, 0))) {
				if (FAILED(lpDDPrim->Blt(&rd, lpDDScreen, &rs, DDBLT_WAIT, 0))) {
					return 1;
				}
			}
		}

		if (validate & 1) {
			// Validate the rectangle we just drew
			POINT c = {0, 0};
			ClientToScreen(hVidWnd, &c);
			rd.left -= c.x; rd.right -= c.x;
			rd.top -= c.y; rd.bottom -= c.y;
			ValidateRect(hVidWnd, &rd);
		}

		return 0;
	}

	int get(void* info)
	{
		InterfaceInfo* pInfo = (InterfaceInfo*)info;
		if (!pInfo) {
			return 1;
		}

		if (vidFullscreen && lpDDBack) {
			IntInfoAddStringModule(pInfo, L"Using a triple buffer");
		} else {
			IntInfoAddStringModule(pInfo, L"Using Blt() to transfer the image");
		}

		IntInfoAddStringModule(pInfo, L"Using video memory for the final blit");

		return 0;
	}

	int set(const wchar_t* name, const void* value)
	{
		return 0;
	}

	VideoDDraw() {
		lpDD = 0;
		lpDDPrim = 0;
		lpDDBack = 0;
		lpDDScreen = 0;
		clipper = 0;

		ddrawHandle = 0;

		rs.left = rs.right = rs.top = rs.bottom = 0;
		rd.left = rd.right = rd.top = rd.bottom = 0;
	}

	~VideoDDraw() {
		exit();
	}
};
